1   /* Copyright 2002-2016 CS Systèmes d'Information
2    * Licensed to CS Systèmes d'Information (CS) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * CS licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *   http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.orekit.forces.drag;
18  
19  import java.io.BufferedReader;
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.io.InputStreamReader;
23  import java.util.Arrays;
24  import java.util.Calendar;
25  import java.util.GregorianCalendar;
26  
27  import org.hipparchus.exception.DummyLocalizable;
28  import org.hipparchus.geometry.euclidean.threed.Vector3D;
29  import org.hipparchus.util.FastMath;
30  import org.orekit.bodies.BodyShape;
31  import org.orekit.bodies.GeodeticPoint;
32  import org.orekit.errors.OrekitException;
33  import org.orekit.errors.OrekitMessages;
34  import org.orekit.frames.Frame;
35  import org.orekit.frames.Transform;
36  import org.orekit.time.AbsoluteDate;
37  import org.orekit.time.TimeScalesFactory;
38  import org.orekit.utils.PVCoordinates;
39  import org.orekit.utils.PVCoordinatesProvider;
40  
41  /** This atmosphere model is the realization of the DTM-2000 model.
42   * <p>
43   * It is described in the paper: <br>
44   *
45   * <b>The DTM-2000 empirical thermosphere model with new data assimilation
46   *  and constraints at lower boundary: accuracy and properties</b><br>
47   *
48   * <i>S. Bruinsma, G. Thuillier and F. Barlier</i> <br>
49   *
50   * Journal of Atmospheric and Solar-Terrestrial Physics 65 (2003) 1053–1070<br>
51   *
52   *</p>
53   * <p>
54   * Two computation methods are proposed to the user:
55   * <ul>
56   *   <li>one OREKIT independent and compliant with initial FORTRAN
57   *       routine entry values:
58   *        {@link #getDensity(int, double, double, double, double, double, double, double, double)}.</li>
59   *   <li> one compliant with OREKIT Atmosphere interface, necessary to the
60   *        {@link org.orekit.forces.drag.DragForce drag force model}
61   *        computation.</li>
62   * </ul>
63   *
64   *<p>
65   * This model provides dense output for altitudes beyond 120 km. Computed data are:
66   * <ul>
67   *   <li>Temperature at altitude z (K)</li>
68   *   <li>Exospheric temperature above input position (K)</li>
69   *   <li>Vertical gradient of T a 120 km</li>
70   *   <li>Total density (kg/m³)</li>
71   *   <li>Mean atomic mass</li>
72   *   <li>Partial densities in (kg/m³) : hydrogen, helium, atomic oxygen,
73   *   molecular nitrogen, molecular oxygen, atomic nitrogen</li>
74   * </ul>
75   *
76   * <p>
77   * The model needs geographical and time information to compute general values,
78   * but also needs space weather data : mean and instantaneous solar flux and
79   * geomagnetic indices.
80   * </p>
81   * <p>
82   * Mean solar flux is (for the moment) represented by the F10.7 indices. Instantaneous
83   * flux can be set to the mean value if the data is not available. Geomagnetic
84   * activity is represented by the Kp indice, which goes from 1 (very low activity) to
85   * 9 (high activity).
86   *
87   * <p>
88   * All these data can be found on the <a href="http://sec.noaa.gov/Data/index.html">
89   * NOAA (National Oceanic and Atmospheric Administration) website.</a>
90   * </p>
91   *
92   *
93   * @author R. Biancale, S. Bruinsma: original fortran routine
94   * @author Fabien Maussion (java translation)
95   */
96  public class DTM2000 implements Atmosphere {
97  
98      /** Identifier for hydrogen. */
99      public static final int HYDROGEN = 1;
100 
101     /** Identifier for helium. */
102     public static final int HELIUM = 2;
103 
104     /** Identifier for atomic oxygen. */
105     public static final int ATOMIC_OXYGEN = 3;
106 
107     /** Identifier for molecular nitrogen. */
108     public static final int MOLECULAR_NITROGEN = 4;
109 
110     /** Identifier for molecular oxygen. */
111     public static final int MOLECULAR_OXYGEN = 5;
112 
113     /** Identifier for atomic nitrogen. */
114     public static final int ATOMIC_NITROGEN = 6;
115 
116     /** Serializable UID. */
117     private static final long serialVersionUID = -8901940398967553588L;
118 
119     // Constants :
120 
121     /** Number of parameters. */
122     private static final int NLATM = 96;
123 
124     /** Thermal diffusion coefficient. */
125     private static final double[] ALEFA = {
126         0, -0.40, -0.38, 0, 0, 0, 0
127     };
128 
129     /** Atomic mass  H, He, O, N2, O2, N. */
130     private static final double[] MA = {
131         0, 1, 4, 16, 28, 32, 14
132     };
133 
134     /** Atomic mass  H, He, O, N2, O2, N. */
135     private static final double[] VMA = {
136         0, 1.6606e-24, 6.6423e-24, 26.569e-24, 46.4958e-24, 53.1381e-24, 23.2479e-24
137     };
138 
139     /** Polar Earth radius. */
140     private static final double RE = 6356.77;
141 
142     /** Reference altitude. */
143     private static final double ZLB0 = 120.0;
144 
145     /** Cosine of the latitude of the magnetic pole (79N, 71W). */
146     private static final double CPMG = .19081;
147 
148     /** Sine of the latitude of the magnetic pole (79N, 71W). */
149     private static final double SPMG = .98163;
150 
151     /** Longitude (in radians) of the magnetic pole (79N, 71W). */
152     private static final double XLMG = -1.2392;
153 
154     /** Gravity acceleration at 120 km altitude. */
155     private static final double GSURF = 980.665;
156 
157     /** Universal gas constant. */
158     private static final double RGAS = 831.4;
159 
160     /** 2 * π / 365. */
161     private static final double ROT = 0.017214206;
162 
163     /** 2 * rot. */
164     private static final double ROT2 = 0.034428412;
165 
166     /** Resources text file. */
167     private static final String DTM2000 = "/assets/org/orekit/dtm_2000.txt";
168 
169     // CHECKSTYLE: stop JavadocVariable check
170 
171     /** Elements coefficients. */
172     private static double[] tt   = null;
173     private static double[] h    = null;
174     private static double[] he   = null;
175     private static double[] o    = null;
176     private static double[] az2  = null;
177     private static double[] o2   = null;
178     private static double[] az   = null;
179     private static double[] t0   = null;
180     private static double[] tp   = null;
181 
182     /** Partial derivatives. */
183     private static double[] dtt  = null;
184     private static double[] dh   = null;
185     private static double[] dhe  = null;
186     private static double[] dox  = null;
187     private static double[] daz2 = null;
188     private static double[] do2  = null;
189     private static double[] daz  = null;
190     private static double[] dt0  = null;
191     private static double[] dtp  = null;
192 
193     // CHECKSTYLE: resume JavadocVariable check
194 
195     /** Number of days in current year. */
196     private int cachedDay;
197 
198     /** Instant solar flux. f[1] = instantaneous flux; f[2] = 0. (not used). */
199     private double[] cachedF = new double[3];
200 
201     /** Mean solar flux. fbar[1] = mean flux; fbar[2] = 0. (not used). */
202     private double[] cachedFbar = new double[3];
203 
204     /** Kp coefficients.
205      * <ul>
206      *   <li>akp[1] = 3-hourly kp</li>
207      *   <li>akp[2] = 0 (not used)</li>
208      *   <li>akp[3] = mean kp of last 24 hours</li>
209      *   <li>akp[4] = 0 (not used)</li>
210      * </ul>
211      */
212     private double[] akp = new double[5];
213 
214     /** Geodetic altitude in km (minimum altitude: 120 km). */
215     private double cachedAlti;
216 
217     /** Local solar time (rad). */
218     private double cachedHl;
219 
220     /** Geodetic Latitude (rad). */
221     private double alat;
222 
223     /** Geodetic longitude (rad). */
224     private double xlon;
225 
226     /** Temperature at altitude z (K). */
227     private double tz;
228 
229     /** Exospheric temperature. */
230     private double tinf;
231 
232     /** Vertical gradient of T a 120 km. */
233     private double tp120;
234 
235     /** Total density (g/cm3). */
236     private double ro;
237 
238     /** Mean atomic mass. */
239     private double wmm;
240 
241     /** Partial densities in (g/cm3).
242      * d(1) = hydrogen
243      * d(2) = helium
244      * d(3) = atomic oxygen
245      * d(4) = molecular nitrogen
246      * d(5) = molecular oxygen
247      * d(6) = atomic nitrogen
248      */
249     private double[] d = new double[7];
250 
251     // CHECKSTYLE: stop JavadocVariable check
252 
253     /** Legendre coefficients. */
254     private double p10;
255     private double p20;
256     private double p30;
257     private double p40;
258     private double p50;
259     private double p60;
260     private double p11;
261     private double p21;
262     private double p31;
263     private double p41;
264     private double p51;
265     private double p22;
266     private double p32;
267     private double p42;
268     private double p52;
269     private double p62;
270     private double p33;
271     private double p10mg;
272     private double p20mg;
273     private double p40mg;
274 
275     /** Local time intermediate values. */
276     private double hl0;
277     private double ch;
278     private double sh;
279     private double c2h;
280     private double s2h;
281     private double c3h;
282     private double s3h;
283 
284     // CHECKSTYLE: resume JavadocVariable check
285 
286     /** Sun position. */
287     private PVCoordinatesProvider sun;
288 
289     /** External data container. */
290     private DTM2000InputParameters inputParams;
291 
292     /** Earth body shape. */
293     private BodyShape earth;
294 
295     /** Simple constructor for independent computation.
296      * @param parameters the solar and magnetic activity data
297      * @param sun the sun position
298      * @param earth the earth body shape
299      * @exception OrekitException if some resource file reading error occurs
300      */
301     public DTM2000(final DTM2000InputParameters parameters,
302                    final PVCoordinatesProvider sun, final BodyShape earth)
303         throws OrekitException {
304         this.earth = earth;
305         this.sun = sun;
306         this.inputParams = parameters;
307         if (tt == null) {
308             readcoefficients();
309         }
310     }
311 
312     /** {@inheritDoc} */
313     public Frame getFrame() {
314         return earth.getBodyFrame();
315     }
316 
317     /** Get the local density with initial entries.
318      * @param day day of year
319      * @param alti altitude in meters
320      * @param lon local longitude (rad)
321      * @param lat local latitude (rad)
322      * @param hl local solar time in rad (O hr = 0 rad)
323      * @param f instantaneous solar flux (F10.7)
324      * @param fbar mean solar flux (F10.7)
325      * @param akp3 3 hrs geomagnetic activity index (1-9)
326      * @param akp24 Mean of last 24 hrs geomagnetic activity index (1-9)
327      * @return the local density (kg/m³)
328      * @exception OrekitException if altitude is outside of supported range
329      */
330     public double getDensity(final int day,
331                              final double alti, final double lon, final double lat,
332                              final double hl, final double f, final double fbar,
333                              final double akp3, final double akp24)
334         throws OrekitException {
335         final double threshold = 120000;
336         if (alti < threshold) {
337             throw new OrekitException(OrekitMessages.ALTITUDE_BELOW_ALLOWED_THRESHOLD,
338                                       alti, threshold);
339         }
340         this.cachedDay  = day;
341         this.cachedAlti = alti / 1000;
342         xlon = lon;
343         alat = lat;
344         this.cachedHl = hl;
345         this.cachedF[1] = f;
346         this.cachedFbar[1] = fbar;
347         akp[1] = akp3;
348         akp[3] = akp24;
349         computation();
350         return ro * 1000;
351     }
352 
353 
354     /** Computes output vales once the inputs are set.
355      */
356     private void computation() {
357         ro = 0.0;
358 
359         final double zlb = ZLB0; // + dzlb ??
360 
361         // compute Legendre polynomials wrt geographic pole
362         final double c = FastMath.sin(alat);
363         final double c2 = c * c;
364         final double c4 = c2 * c2;
365         final double s = FastMath.cos(alat);
366         final double s2 = s * s;
367         p10 = c;
368         p20 = 1.5 * c2 - 0.5;
369         p30 = c * (2.5 * c2 - 1.5);
370         p40 = 4.375 * c4 - 3.75 * c2 + 0.375;
371         p50 = c * (7.875 * c4 - 8.75 * c2 + 1.875);
372         p60 = (5.5 * c * p50 - 2.5 * p40) / 3.0;
373         p11 = s;
374         p21 = 3.0 * c * s;
375         p31 = s * (7.5 * c2 - 1.5);
376         p41 = c * s * (17.5 * c2 - 7.5);
377         p51 = s * (39.375 * c4 - 26.25 * c2 + 1.875);
378         p22 = 3.0 * s2;
379         p32 = 15.0 * c * s2;
380         p42 = s2 * (52.5 * c2 - 7.5);
381         p52 = 3.0 * c * p42 - 2.0 * p32;
382         p62 = 2.75 * c * p52 - 1.75 * p42;
383         p33 = 15.0 * s * s2;
384 
385         // compute Legendre polynomials wrt magnetic pole (79N, 71W)
386         final double clmlmg = FastMath.cos(xlon - XLMG);
387         final double cmg  = s * CPMG * clmlmg + c * SPMG;
388         final double cmg2 = cmg * cmg;
389         final double cmg4 = cmg2 * cmg2;
390         p10mg = cmg;
391         p20mg = 1.5 * cmg2 - 0.5;
392         p40mg = 4.375 * cmg4 - 3.75 * cmg2 + 0.375;
393 
394         // local time
395         hl0 = cachedHl;
396         ch  = FastMath.cos(hl0);
397         sh  = FastMath.sin(hl0);
398         c2h = ch * ch - sh * sh;
399         s2h = 2.0 * ch * sh;
400         c3h = c2h * ch - s2h * sh;
401         s3h = s2h * ch + c2h * sh;
402 
403         //  compute function g(l) / tinf, t120, tp120
404         int kleq = 1;
405         final double gdelt = gFunction(tt, dtt, 1, kleq);
406         dtt[1] = 1.0 + gdelt;
407         tinf   = tt[1] * dtt[1];
408 
409         kleq = 0; // equinox
410 
411         if ((cachedDay < 59) || (cachedDay > 284)) {
412             kleq = -1; // north winter
413         }
414         if ((cachedDay > 99) && (cachedDay < 244)) {
415             kleq = 1; // north summer
416         }
417 
418         final double gdelt0 =  gFunction(t0, dt0, 0, kleq);
419         dt0[1] = (t0[1] + gdelt0) / t0[1];
420         final double t120 = t0[1] + gdelt0;
421         final double gdeltp = gFunction(tp, dtp, 0, kleq);
422         dtp[1] = (tp[1] + gdeltp) / tp[1];
423         tp120 = tp[1] + gdeltp;
424 
425         // compute n(z) concentrations: H, He, O, N2, O2, N
426         final double sigma   = tp120 / (tinf - t120);
427         final double dzeta   = (RE + zlb) / (RE + cachedAlti);
428         final double zeta    = (cachedAlti - zlb) * dzeta;
429         final double sigzeta = sigma * zeta;
430         final double expsz   = FastMath.exp(-sigzeta);
431         tz = tinf - (tinf - t120) * expsz;
432 
433         final double[] dbase = new double[7];
434 
435         kleq = 1;
436 
437         final double gdelh = gFunction(h, dh, 0, kleq);
438         dh[1] = FastMath.exp(gdelh);
439         dbase[1] = h[1] * dh[1];
440 
441         final double gdelhe = gFunction(he, dhe, 0, kleq);
442         dhe[1] = FastMath.exp(gdelhe);
443         dbase[2] = he[1] * dhe[1];
444 
445         final double gdelo = gFunction(o, dox, 1, kleq);
446         dox[1] = FastMath.exp(gdelo);
447         dbase[3] = o[1] * dox[1];
448 
449         final double gdelaz2 = gFunction(az2, daz2, 1, kleq);
450         daz2[1] = FastMath.exp(gdelaz2);
451         dbase[4] = az2[1] * daz2[1];
452 
453         final double gdelo2 = gFunction(o2, do2, 1, kleq);
454         do2[1] = FastMath.exp(gdelo2);
455         dbase[5] = o2[1] * do2[1];
456 
457         final double gdelaz = gFunction(az, daz, 1, kleq);
458         daz[1] = FastMath.exp(gdelaz);
459         dbase[6] = az[1] * daz[1];
460 
461         final double zlbre  = 1.0 + zlb / RE;
462         final double glb    = (GSURF / (zlbre * zlbre)) / (sigma * RGAS * tinf);
463         final double t120tz = t120 / tz;
464 
465         final double[] cc = new double[7];
466         final double[] fz = new double[7];
467 
468         for (int i = 1; i <= 6; i++) {
469             final double gamma = MA[i] * glb;
470             final double upapg = 1.0 + ALEFA[i] + gamma;
471             fz[i] = FastMath.pow(t120tz, upapg) * FastMath.exp(-sigzeta * gamma);
472             // concentrations of H, He, O, N2, O2, N (particles/cm³)
473             cc[i] = dbase[i] * fz[i];
474             // densities of H, He, O, N2, O2, N (g/cm³)
475             d[i]  = cc[i] * VMA[i];
476             // total density
477             ro += d[i];
478         }
479 
480         // mean atomic mass
481         wmm = ro / (VMA[1] * (cc[1] + cc[2] + cc[3] + cc[4] + cc[5] + cc[6]));
482 
483     }
484 
485     /** Computation of function G.
486      * @param a vector of coefficients for computation
487      * @param da vector of partial derivatives
488      * @param ff0 coefficient flag (1 for Ox, Az, He, T°; 0 for H and tp120)
489      * @param kle_eq season indicator flag (summer, winter, equinox)
490      * @return value of G
491      */
492     private double gFunction(final double[] a, final double[] da,
493                              final int ff0, final int kle_eq) {
494 
495         final double[] fmfb   = new double[3];
496         final double[] fbm150 = new double[3];
497 
498         // latitude terms
499         da[2]  = p20;
500         da[3]  = p40;
501         da[74] = p10;
502         double a74 = a[74];
503         double a77 = a[77];
504         double a78 = a[78];
505         if (kle_eq == -1) {
506             // winter
507             a74 = -a74;
508             a77 = -a77;
509             a78 = -a78;
510         }
511         if (kle_eq == 0 ) {
512             // equinox
513             a74 = semestrialCorrection(a74);
514             a77 = semestrialCorrection(a77);
515             a78 = semestrialCorrection(a78);
516         }
517         da[77] = p30;
518         da[78] = p50;
519         da[79] = p60;
520 
521         // flux terms
522         fmfb[1]   = cachedF[1] - cachedFbar[1];
523         fmfb[2]   = cachedF[2] - cachedFbar[2];
524         fbm150[1] = cachedFbar[1] - 150.0;
525         fbm150[2] = cachedFbar[2];
526         da[4]     = fmfb[1];
527         da[6]     = fbm150[1];
528         da[4]     = da[4] + a[70] * fmfb[2];
529         da[6]     = da[6] + a[71] * fbm150[2];
530         da[70]    = fmfb[2] * (a[4] + 2.0 * a[5] * da[4] + a[82] * p10 +
531                                a[83] * p20 + a[84] * p30);
532         da[71]    = fbm150[2] * (a[6] + 2.0 * a[69] * da[6] + a[85] * p10 +
533                                  a[86] * p20 + a[87] * p30);
534         da[5]     = da[4] * da[4];
535         da[69]    = da[6] * da[6];
536         da[82]    = da[4] * p10;
537         da[83]    = da[4] * p20;
538         da[84]    = da[4] * p30;
539         da[85]    = da[6] * p20;
540         da[86]    = da[6] * p30;
541         da[87]    = da[6] * p40;
542 
543         // Kp terms
544         final int ikp  = 62;
545         final int ikpm = 67;
546         final double c2fi = 1.0 - p10mg * p10mg;
547         final double dkp  = akp[1] + (a[ikp] + c2fi * a[ikp + 1]) * akp[2];
548         double dakp = a[7] + a[8] * p20mg + a[68] * p40mg +
549                       2.0 * dkp * (a[60] + a[61] * p20mg +
550                                    a[75] * 2.0 * dkp * dkp);
551         da[ikp] = dakp * akp[2];
552         da[ikp + 1] = da[ikp] * c2fi;
553         final double dkpm  = akp[3] + a[ikpm] * akp[4];
554         final double dakpm = a[64] + a[65] * p20mg + a[72] * p40mg +
555                              2.0 * dkpm * (a[66] + a[73] * p20mg +
556                                            a[76] * 2.0 * dkpm * dkpm);
557         da[ikpm] = dakpm * akp[4];
558         da[7]    = dkp;
559         da[8]    = p20mg * dkp;
560         da[68]   = p40mg * dkp;
561         da[60]   = dkp * dkp;
562         da[61]   = p20mg * da[60];
563         da[75]   = da[60] * da[60];
564         da[64]   = dkpm;
565         da[65]   = p20mg * dkpm;
566         da[72]   = p40mg * dkpm;
567         da[66]   = dkpm * dkpm;
568         da[73]   = p20mg * da[66];
569         da[76]   = da[66] * da[66];
570 
571         // non-periodic g(l) function
572         double f0 = a[4]  * da[4]  + a[5]  * da[5]  + a[6]  * da[6]  +
573                     a[69] * da[69] + a[82] * da[82] + a[83] * da[83] +
574                     a[84] * da[84] + a[85] * da[85] + a[86] * da[86] +
575                     a[87] * da[87];
576         final double f1f = 1.0 + f0 * ff0;
577 
578         f0 = f0 + a[2] * da[2] + a[3] * da[3] + a74 * da[74] +
579              a77 * da[77] + a[7] * da[7] + a[8] * da[8] +
580              a[60] * da[60] + a[61] * da[61] + a[68] * da[68] +
581              a[64] * da[64] + a[65] * da[65] + a[66] * da[66] +
582              a[72] * da[72] + a[73] * da[73] + a[75] * da[75] +
583              a[76] * da[76] + a78   * da[78] + a[79] * da[79];
584 //      termes annuels symetriques en latitude
585         da[9]  = FastMath.cos(ROT * (cachedDay - a[11]));
586         da[10] = p20 * da[9];
587 //      termes semi-annuels symetriques en latitude
588         da[12] = FastMath.cos(ROT2 * (cachedDay - a[14]));
589         da[13] = p20 * da[12];
590 //      termes annuels non symetriques en latitude
591         final double coste = FastMath.cos(ROT * (cachedDay - a[18]));
592         da[15] = p10 * coste;
593         da[16] = p30 * coste;
594         da[17] = p50 * coste;
595 //      terme  semi-annuel  non symetrique  en latitude
596         final double cos2te = FastMath.cos(ROT2 * (cachedDay - a[20]));
597         da[19] = p10 * cos2te;
598         da[39] = p30 * cos2te;
599         da[59] = p50 * cos2te;
600 //      termes diurnes [et couples annuel]
601         da[21] = p11 * ch;
602         da[22] = p31 * ch;
603         da[23] = p51 * ch;
604         da[24] = da[21] * coste;
605         da[25] = p21 * ch * coste;
606         da[26] = p11 * sh;
607         da[27] = p31 * sh;
608         da[28] = p51 * sh;
609         da[29] = da[26] * coste;
610         da[30] = p21 * sh * coste;
611 //      termes semi-diurnes [et couples annuel]
612         da[31] = p22 * c2h;
613         da[37] = p42 * c2h;
614         da[32] = p32 * c2h * coste;
615         da[33] = p22 * s2h;
616         da[38] = p42 * s2h;
617         da[34] = p32 * s2h * coste;
618         da[88] = p32 * c2h;
619         da[89] = p32 * s2h;
620         da[90] = p52 * c2h;
621         da[91] = p52 * s2h;
622         double a88 = a[88];
623         double a89 = a[89];
624         double a90 = a[90];
625         double a91 = a[91];
626         if (kle_eq == -1) {            //hiver
627             a88 = -a88;
628             a89 = -a89;
629             a90 = -a90;
630             a91 = -a91;
631         }
632         if (kle_eq == 0) {             //equinox
633             a88 = semestrialCorrection(a88);
634             a89 = semestrialCorrection(a89);
635             a90 = semestrialCorrection(a90);
636             a91 = semestrialCorrection(a91);
637         }
638         da[92] = p62 * c2h;
639         da[93] = p62 * s2h;
640 //      termes ter-diurnes
641         da[35] = p33 * c3h;
642         da[36] = p33 * s3h;
643 //      fonction g[l] periodique
644         double fp = a[9]  * da[9]  + a[10] * da[10] + a[12] * da[12] + a[13] * da[13] +
645                     a[15] * da[15] + a[16] * da[16] + a[17] * da[17] + a[19] * da[19] +
646                     a[21] * da[21] + a[22] * da[22] + a[23] * da[23] + a[24] * da[24] +
647                     a[25] * da[25] + a[26] * da[26] + a[27] * da[27] + a[28] * da[28] +
648                     a[29] * da[29] + a[30] * da[30] + a[31] * da[31] + a[32] * da[32] +
649                     a[33] * da[33] + a[34] * da[34] + a[35] * da[35] + a[36] * da[36] +
650                     a[37] * da[37] + a[38] * da[38] + a[39] * da[39] + a[59] * da[59] +
651                     a88   * da[88] + a89   * da[89] + a90   * da[90] + a91   * da[91] +
652                     a[92] * da[92] + a[93] * da[93];
653 //      termes d'activite magnetique
654         da[40] = p10 * coste * dkp;
655         da[41] = p30 * coste * dkp;
656         da[42] = p50 * coste * dkp;
657         da[43] = p11 * ch * dkp;
658         da[44] = p31 * ch * dkp;
659         da[45] = p51 * ch * dkp;
660         da[46] = p11 * sh * dkp;
661         da[47] = p31 * sh * dkp;
662         da[48] = p51 * sh * dkp;
663 
664 //      fonction g[l] periodique supplementaire
665         fp += a[40] * da[40] + a[41] * da[41] + a[42] * da[42] + a[43] * da[43] +
666               a[44] * da[44] + a[45] * da[45] + a[46] * da[46] + a[47] * da[47] +
667               a[48] * da[48];
668 
669         dakp = (a[40] * p10 + a[41] * p30 + a[42] * p50) * coste +
670                (a[43] * p11 + a[44] * p31 + a[45] * p51) * ch +
671                (a[46] * p11 + a[47] * p31 + a[48] * p51) * sh;
672         da[ikp] += dakp * akp[2];
673         da[ikp + 1] = da[ikp] + dakp * c2fi * akp[2];
674 //      termes de longitude
675         final double clfl = FastMath.cos(xlon);
676         da[49] = p11 * clfl;
677         da[50] = p21 * clfl;
678         da[51] = p31 * clfl;
679         da[52] = p41 * clfl;
680         da[53] = p51 * clfl;
681         final double slfl = FastMath.sin(xlon);
682         da[54] = p11 * slfl;
683         da[55] = p21 * slfl;
684         da[56] = p31 * slfl;
685         da[57] = p41 * slfl;
686         da[58] = p51 * slfl;
687 
688 //      fonction g[l] periodique supplementaire
689         fp += a[49] * da[49] + a[50] * da[50] + a[51] * da[51] + a[52] * da[52] +
690               a[53] * da[53] + a[54] * da[54] + a[55] * da[55] + a[56] * da[56] +
691               a[57] * da[57] + a[58] * da[58];
692 
693 //      fonction g(l) totale (couplage avec le flux)
694         return f0 + fp * f1f;
695 
696     }
697 
698 
699     /** Apply a correction coefficient to the given parameter.
700      * @param param the parameter to correct
701      * @return the corrected parameter
702      */
703     private double semestrialCorrection(final double param) {
704         final int debeq_pr = 59;
705         final int debeq_au = 244;
706         final double result;
707         if (cachedDay >= 100) {
708             final double xmult  = (cachedDay - debeq_au) / 40.0;
709             result = param - 2.0 * param * xmult;
710         } else {
711             final double xmult  = (cachedDay - debeq_pr) / 40.0;
712             result = 2.0 * param * xmult - param;
713         }
714         return result;
715     }
716 
717 
718     /** Store the DTM model elements coefficients in internal arrays.
719      * @exception OrekitException if some resource file reading error occurs
720      */
721     private static void readcoefficients() throws OrekitException {
722 
723         final int size = NLATM + 1;
724         tt   = new double[size];
725         h    = new double[size];
726         he   = new double[size];
727         o    = new double[size];
728         az2  = new double[size];
729         o2   = new double[size];
730         az   = new double[size];
731         t0   = new double[size];
732         tp   = new double[size];
733         dtt  = new double[size];
734         dh   = new double[size];
735         dhe  = new double[size];
736         dox  = new double[size];
737         daz2 = new double[size];
738         do2  = new double[size];
739         daz  = new double[size];
740         dt0  = new double[size];
741         dtp  = new double[size];
742 
743         Arrays.fill(dtt,  Double.NaN);
744         Arrays.fill(dh,   Double.NaN);
745         Arrays.fill(dhe,  Double.NaN);
746         Arrays.fill(dox,  Double.NaN);
747         Arrays.fill(daz2, Double.NaN);
748         Arrays.fill(do2,  Double.NaN);
749         Arrays.fill(daz,  Double.NaN);
750         Arrays.fill(dt0,  Double.NaN);
751         Arrays.fill(dtp,  Double.NaN);
752 
753         final InputStream in = DTM2000.class.getResourceAsStream(DTM2000);
754         if (in == null) {
755             throw new OrekitException(OrekitMessages.UNABLE_TO_FIND_RESOURCE, DTM2000);
756         }
757 
758         BufferedReader r = null;
759         try {
760 
761             r = new BufferedReader(new InputStreamReader(in, "UTF-8"));
762             r.readLine();
763             r.readLine();
764             for (String line = r.readLine(); line != null; line = r.readLine()) {
765                 final int num = Integer.parseInt(line.substring(0, 4).replace(' ', '0'));
766                 line = line.substring(4);
767                 tt[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
768                 line = line.substring(13 + 9);
769                 h[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
770                 line = line.substring(13 + 9);
771                 he[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
772                 line = line.substring(13 + 9);
773                 o[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
774                 line = line.substring(13 + 9);
775                 az2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
776                 line = line.substring(13 + 9);
777                 o2[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
778                 line = line.substring(13 + 9);
779                 az[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
780                 line = line.substring(13 + 9);
781                 t0[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
782                 line = line.substring(13 + 9);
783                 tp[num] = Double.parseDouble(line.substring(0, 13).replace(' ', '0'));
784             }
785         } catch (IOException ioe) {
786             throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
787         } finally {
788             if (r != null) {
789                 try {
790                     r.close();
791                 } catch (IOException ioe) {
792                     throw new OrekitException(ioe, new DummyLocalizable(ioe.getMessage()));
793                 }
794             }
795         }
796     }
797 
798 
799 
800     /** Get the current exospheric temperature above input position.
801      * {@link #getDensity(int, double, double, double, double, double, double, double, double) getDensity}
802      * method <b>must</b> be called before calling this function.
803      * @return the exospheric temperature (K)
804      */
805     public double getTinf() {
806         return tinf;
807     }
808 
809     /** Get the local temperature.
810      * {@link #getDensity(int, double, double, double, double, double, double, double, double) getDensity}
811      * method <b>must</b> be called before calling this function.
812      * @return the temperature at altitude z (K)
813      */
814     public double getT() {
815         return tz;
816     }
817 
818     /** Get the local mean atomic mass.
819      * {@link #getDensity(int, double, double, double, double, double, double, double, double) getDensity}
820      * method <b>must</b> be called before calling this function.
821      * @return the local mean atomic mass
822      */
823     public double getMam() {
824         return wmm;
825     }
826 
827     /** Get the local partial density of the selected element.
828      * {@link #getDensity(int, double, double, double, double, double, double, double, double) getDensity}
829      * method <b>must</b> be called before calling this function.
830      * @param identifier one of the six elements : {@link #HYDROGEN}, {@link #HELIUM},
831      * {@link #ATOMIC_OXYGEN}, {@link #MOLECULAR_NITROGEN},  {@link #MOLECULAR_OXYGEN}, {@link #ATOMIC_NITROGEN}
832      * @return the local partial density (kg/m³)
833      */
834     public double getPartialDensities(final int identifier) {
835         if (identifier < 1 || identifier > 6) {
836             throw new IllegalArgumentException("element identifier is not correct");
837         }
838         return d[identifier] * 1000;
839     }
840 
841     /** Get the local density.
842      * @param date current date
843      * @param position current position in frame
844      * @param frame the frame in which is defined the position
845      * @return local density (kg/m³)
846      * @exception OrekitException if date is out of range of solar activity model
847      * or if some frame conversion cannot be performed
848      */
849     public double getDensity(final AbsoluteDate date, final Vector3D position,
850                              final Frame frame)
851         throws OrekitException {
852 
853         // check if data are available :
854         if ((date.compareTo(inputParams.getMaxDate()) > 0) ||
855             (date.compareTo(inputParams.getMinDate()) < 0)) {
856             throw new OrekitException(OrekitMessages.NO_SOLAR_ACTIVITY_AT_DATE,
857                                       date, inputParams.getMinDate(), inputParams.getMaxDate());
858         }
859 
860         // compute day number in current year
861         final Calendar cal = new GregorianCalendar();
862         cal.setTime(date.toDate(TimeScalesFactory.getUTC()));
863         final int day = cal.get(Calendar.DAY_OF_YEAR);
864         //position in ECEF so we only have to do the transform once
865         final Frame ecef = earth.getBodyFrame();
866         final Vector3D pEcef = frame.getTransformTo(ecef, date)
867                 .transformPosition(position);
868         // compute geodetic position
869         final GeodeticPoint inBody = earth.transform(pEcef, ecef, date);
870         final double alti = inBody.getAltitude();
871         final double lon = inBody.getLongitude();
872         final double lat = inBody.getLatitude();
873 
874         // compute local solar time
875         final Vector3D sunPos = sun.getPVCoordinates(date, ecef).getPosition();
876         final double hl = FastMath.PI + FastMath.atan2(
877                 sunPos.getX() * pEcef.getY() - sunPos.getY() * pEcef.getX(),
878                 sunPos.getX() * pEcef.getX() + sunPos.getY() * pEcef.getY());
879 
880         // get current solar activity data and compute
881         return getDensity(day, alti, lon, lat, hl, inputParams.getInstantFlux(date),
882                           inputParams.getMeanFlux(date), inputParams.getThreeHourlyKP(date),
883                           inputParams.get24HoursKp(date));
884 
885     }
886 
887     /** Get the inertial velocity of atmosphere molecules.
888      * Here the case is simplified : atmosphere is supposed to have a null velocity
889      * in earth frame.
890      * @param date current date
891      * @param position current position in frame
892      * @param frame the frame in which is defined the position
893      * @return velocity (m/s) (defined in the same frame as the position)
894      * @exception OrekitException if some frame conversion cannot be performed
895      */
896     public Vector3D getVelocity(final AbsoluteDate date, final Vector3D position,
897                                 final Frame frame)
898         throws OrekitException {
899         final Transform bodyToFrame = earth.getBodyFrame().getTransformTo(frame, date);
900         final Vector3D posInBody = bodyToFrame.getInverse().transformPosition(position);
901         final PVCoordinates pvBody = new PVCoordinates(posInBody, new Vector3D(0, 0, 0));
902         final PVCoordinates pvFrame = bodyToFrame.transformPVCoordinates(pvBody);
903         return pvFrame.getVelocity();
904     }
905 
906 }